package org.osflash.signals;

import asunit.asserts.*;
import asunit.framework.IAsync;
import org.osflash.signals.events.GenericEvent;
import org.osflash.signals.events.IBubbleEventHandler;
import org.osflash.signals.events.IEvent;
import org.osflash.signals.DeluxeSignal;
import org.osflash.signals.events.IBubbleEventHandler;
import org.osflash.signals.events.IEvent;
import flash.errors.IllegalOperationError;

class DeluxeSignalWithBubblingEventTest implements IBubbleEventHandler {

	@:meta(Inject())
	public var async : IAsync;
	var theParent : IBubbleEventHandler;
	var theChild : Child;
	var theGrandChild : Child;
	var cancelTimeout : Dynamic;
	@:meta(Before())
	public function setUp() : Void {
		theParent = this;
		theChild = new Child(this, "theChild");
		theGrandChild = new Child(theChild, "theGrandChild");
	}

	@:meta(After())
	public function tearDown() : Void {
		theChild = null;
		theGrandChild = null;
		cancelTimeout = null;
	}

	@:meta(Test())
	public function parent_child_relationships() : Void {
		assertSame("theChild's parent is this", this, theChild.parent);
		assertTrue("this can handle bubbling events", Std.is(this, IBubbleEventHandler));
	}

	@:meta(Test())
	public function dispatch_bubbling_event_from_theGrandChild_should_bubble_to_parent_IBubbleHandler() : Void {
		// If cancelTimeout() isn't called, this test will fail.
		cancelTimeout = async.add(null, 10);
		var event : IEvent = new GenericEvent();
		event.bubbles = true;
		theGrandChild.completed.dispatch(event);
	}

	public function onEventBubbled(e : IEvent) : Bool {
		cancelTimeout();
		assertSame("e.target should be the object that originally dispatched event", theGrandChild, e.target);
		assertSame("e.currentTarget should be the object receiving the bubbled event", this, e.currentTarget);
		return false;
	}

	@:meta(Test())
	public function returning_false_from_onEventBubbled_should_stop_bubbling() : Void {
		var bubbleHater : BubbleHater = new BubbleHater();
		theChild = new Child(bubbleHater, "bubblePopper");
		theChild.popsBubbles = true;
		theGrandChild = new Child(theChild, "bubbleBlower");
		var bubblingEvent : IEvent = new GenericEvent(true);
		// Will only complete without error if theChild pops the bubble.
		theGrandChild.completed.dispatch(bubblingEvent);
	}

	@:meta(Test(expects="flash.errors.IllegalOperationError"))
	public function returning_true_from_onEventBubbled_should_continue_bubbling() : Void {
		var bubbleHater : BubbleHater = new BubbleHater();
		theChild = new Child(bubbleHater, "bubblePopper");
		// Changing popsBubbles to false will fail the test nicely.
		theChild.popsBubbles = false;
		theGrandChild = new Child(theChild, "bubbleBlower");
		var bubblingEvent : IEvent = new GenericEvent(true);
		// Because theChild didn't pop the bubble, this causes bubbleHater to throw an error.
		theGrandChild.completed.dispatch(bubblingEvent);
	}

}

class Child implements IBubbleEventHandler {

	public var parent : Dynamic;
	public var completed : DeluxeSignal;
	public var name : String;
	public var popsBubbles : Bool;
	public function new(parent : Dynamic = null, name : String = "") {
		popsBubbles = false;
		this.parent = parent;
		this.name = name;
		completed = new DeluxeSignal(this);
	}

	public function toString() : String {
		return "[Child " + name + "]";
	}

	public function onEventBubbled(event : IEvent) : Bool {
		return !popsBubbles;
	}

}

class BubbleHater implements IBubbleEventHandler {

	public function onEventBubbled(event : IEvent) : Bool {
		throw new IllegalOperationError("I SAID NO BUBBLES!!!");
		return false;
	}

}

